﻿#region File and License Information

/*
<File>
	<Copyright>Copyright © 2009, Outcoder. All rights reserved.</Copyright>
	<License>
	This file is part of Outcoder.Logging.NLogStrategy

    Outcoder.Logging.NLogStrategy is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Outcoder.Logging.NLogStrategy is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with Outcoder.Logging.NLogStrategy.  If not, see http://www.gnu.org/licenses/.
	</License>
	<Owner Name="Daniel Vaughan" Email="danielvaughan@outcoder.com"/>
	<CreationDate>2010-03-22 17:41:02Z</CreationDate>
</File>
*/

#endregion

using System;
using System.Runtime.CompilerServices;

using NLog;

namespace Outcoder.Logging.LogStrategies
{
	/// <summary>
	/// The <see cref="ILogStrategy"/> for NLog.
	/// </summary>
	public class NLogStrategy : ILogStrategy
	{
		static readonly Logger defaultLog = LogManager.GetCurrentClassLogger();

		/// <summary>
		/// Logs the specified client log entry.
		/// <seealso cref="IServerLogEntry"/>
		/// </summary>
		/// <param name="logEntry">The log entry.</param>
		public void Write(IClientLogEntry logEntry)
		{
			Logger log = defaultLog;
			if (logEntry.LogName != null)
			{
				log = LogManager.GetLogger(logEntry.LogName);
			}

			/* Create a NLog event data instance, 
			 * and populates it with our log entry information. */
			var data = new LogEventInfo();

			data.Level = GetNLogLevel(logEntry.LogLevel);
			data.TimeStamp = logEntry.OccuredAt;

			if (logEntry.ExceptionMemento != null)
			{
				/* Use the exception memento to write 
				 * the message and stack trace etc. */
				data.Context.Add("ExceptionMemento", logEntry.ExceptionMemento.ToString());
			}

			ICodeLocation location = logEntry.CodeLocation;
			if (location != null)
			{
				data.Context.Add("LocationInfo-ClassName", location.ClassName);
				data.Context.Add("LocationInfo-MethodName", location.MethodName);
				data.Context.Add("LocationInfo-FileName", location.FileName);
				data.Context.Add("LocationInfo-LineNumber", location.LineNumber.ToString());
			}

			data.LoggerName = logEntry.LogName;
			data.Message = string.Format("{0}\nMachineName:{1} - ThreadName:(2) - UserName:(3)", logEntry.Message, logEntry.MachineName, logEntry.ThreadName,
			                             logEntry.UserName);

			/* Copy custom properties into Nlog Properties. */
			if (logEntry.Properties != null && logEntry.Properties.Count > 0)
			{
				foreach (var prop in logEntry.Properties)
				{
					data.Context.Add(prop.Key, prop.Value);
				}
			}

			WriteThreadSafe(log, data);
		}

		/// <summary>
		/// Logs the specified server log entry.
		/// <seealso cref="IServerLogEntry"/>
		/// </summary>
		/// <param name="logEntry">The log entry.</param>
		public void Write(IServerLogEntry logEntry)
		{
			Logger log = defaultLog;
			if (logEntry.LogName != null)
			{
				log = LogManager.GetLogger(logEntry.LogName);
			}

			var data = new LogEventInfo();
			Exception exception = logEntry.Exception;
			if (exception != null)
			{
				data.Exception = exception;
			}
			data.Context.Add("Identity", logEntry.Identity);
			data.Level = GetNLogLevel(logEntry.LogLevel);
			ICodeLocation location = logEntry.CodeLocation;
			if (location != null)
			{
				data.Context.Add("LocationInfo-ClassName", location.ClassName);
				data.Context.Add("LocationInfo-MethodName", location.MethodName);
				data.Context.Add("LocationInfo-FileName", location.FileName);
				data.Context.Add("LocationInfo-LineNumber", location.LineNumber.ToString());
			}

			data.LoggerName = logEntry.LogName;
			data.Message = string.Format("{0}\nMachineName:{1} - ThreadName:(2) - UserName:(3)", logEntry.Message, logEntry.MachineName, logEntry.ThreadName,
			                             logEntry.UserName);
			data.TimeStamp = logEntry.OccuredAt;

			/* Copy custom properties into Nlog Properties. */
			if (logEntry.Properties != null && logEntry.Properties.Count > 0)
			{
				foreach (var prop in logEntry.Properties)
				{
					data.Context.Add(prop.Key, prop.Value);
				}
			}

			WriteThreadSafe(log, data);
		}

		/// <summary> 
		/// All Write log calls are done asynchronously because the Outcoder.Logging.Log 
		/// uses the AppPool to dispatch calls to this method. Therefore we need to ensure 
		/// that the call to Log is threadsafe. That is, if an appender such as FileAppender is used, 
		/// then we need to ensure it is called from no more than one thread at a time. 
		/// </summary>
		/// <param name="logger">The logger.</param>
		/// <param name="loggingEvent">The logging event.</param>
		[MethodImpl(MethodImplOptions.Synchronized)]
		static void WriteThreadSafe(Logger logger, LogEventInfo loggingEvent)
		{
			logger.Log(loggingEvent);
		}

		/// <summary>
		/// Gets the log level for the log with the specified name.
		/// </summary>
		/// <param name="clientInfo">Caller information.</param>
		/// <returns>The threshold log level.</returns>
		public LogLevel GetLogLevel(IClientInfo clientInfo)
		{
			if (clientInfo == null)
			{
				return LogLevel;
			}

			Logger log = LogManager.GetLogger(clientInfo.LogName);
			return GetLogLevel(log);
		}

		#region Default log level

		LogLevel? lowestLogLevel;
		readonly object lowestLogLevelLock = new object();

		LogLevel LogLevel
		{
			get
			{
				if (!lowestLogLevel.HasValue)
				{
					lock (lowestLogLevelLock)
					{
						if (!lowestLogLevel.HasValue)
						{
							Logger log = LogManager.GetCurrentClassLogger();
							lowestLogLevel = LogLevel.None;
							LogLevel logLevel = GetLogLevel(log);
							if (logLevel < lowestLogLevel)
							{
								lowestLogLevel = logLevel;
							}
						}
					}
				}

				return lowestLogLevel.Value;
			}
		}

		#endregion

		/// <summary>
		/// Gets equivalent Clog log level of the NLog log.
		/// </summary>
		/// <param name="log">The log.</param>
		/// <returns></returns>
		static LogLevel GetLogLevel(Logger log)
		{
			if (log.IsDebugEnabled)
			{
				return LogLevel.Debug;
			}
			if (log.IsInfoEnabled)
			{
				return LogLevel.Info;
			}
			if (log.IsWarnEnabled)
			{
				return LogLevel.Warn;
			}
			if (log.IsErrorEnabled)
			{
				return LogLevel.Error;
			}
			if (log.IsFatalEnabled)
			{
				return LogLevel.Fatal;
			}
			return LogLevel.None;
		}

		/// <summary>
		/// Converts a Clog LogLevel to the NLog level equivalent.
		/// </summary>
		/// <param name="level">The level to convert.</param>
		/// <returns>The log4net level.</returns>
		static NLog.LogLevel GetNLogLevel(LogLevel level)
		{
			switch (level)
			{
				case LogLevel.Debug:
					return NLog.LogLevel.Debug;
				case LogLevel.Info:
					return NLog.LogLevel.Info;
				case LogLevel.Warn:
					return NLog.LogLevel.Warn;
				case LogLevel.Error:
					return NLog.LogLevel.Error;
				case LogLevel.Fatal:
					return NLog.LogLevel.Fatal;
				default:
					return NLog.LogLevel.Off;
			}
		}
	}
}
